﻿using System;
using System.Linq;
using System.Xml.Linq;
using System.Collections.Generic;
using System.IO;
using System.Device.Location;
using System.Windows.Threading;

namespace TransportMe
{
    public class FakeGps : IGeoPositionWatcher<GeoCoordinate>
    {
        List<RoutePoint> _points = new List<RoutePoint>();
        GeoCoordinate _currentPosition;
        double _speed;
        int _updateInterval;
        int _pause;
        int _currentWayPoint;
        int _currentIndex;
        DateTime _lastUpdate;
        DispatcherTimer _timer;
        GeoPositionStatus _currentStatus;

        public event EventHandler<GeoPositionStatusChangedEventArgs> StatusChanged;
        public event EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>> PositionChanged;

        public const string ROUTE = @"
<?xml version='1.0' encoding='UTF-8' ?>
<routes update_interval='500' pause='2000' meters_per_second='1771.16807152899' >
   <route lat='27.75687' lon='-82.67818' duration='1.3' heading='63.4' />
   <route lat='27.76598' lon='-82.65758' duration='3.4' heading='351.6' />
   <route lat='27.81943' lon='-82.66650' duration='3.5' heading='2.5' />
   <route lat='27.87468' lon='-82.66376' duration='3.0' heading='42.4' />
   <route lat='27.90988' lon='-82.62737' duration='5.8' heading='66.4' />
   <route lat='27.94689' lon='-82.53123' duration='1.7' heading='327.0' />
   <route lat='27.96933' lon='-82.54771' duration='4.1' heading='1.1' />
   <route lat='28.03541' lon='-82.54634' duration='1.2' heading='299.1' />
   <route lat='28.04450' lon='-82.56488' duration='0.5' heading='318.4' />
   <route lat='28.04996' lon='-82.57037' duration='2.1' heading='12.5' />
   <route lat='28.08267' lon='-82.56213' duration='1.0' heading='64.4' />
   <route lat='28.08934' lon='-82.54634' duration='2.5' heading='357.4' />
   <route lat='28.12931' lon='-82.54840' duration='0.0' heading='0.0' />
</routes>
";

        public FakeGps()
        {
            _lastUpdate = DateTime.MinValue;
            _currentStatus = GeoPositionStatus.NoData;

            _timer = new DispatcherTimer();
            _timer.Tick += _timer_Tick;

            using (var rdr = new StringReader(FakeGps.ROUTE.Trim()))
            {
                var doc = XDocument.Load(rdr);
                _updateInterval = (from waypoint in doc.Elements("routes") select Convert.ToInt32(waypoint.Attribute("update_interval").Value)).First();
                _pause = (from waypoint in doc.Elements("routes") select Convert.ToInt32(waypoint.Attribute("pause").Value)).First();
                _speed = (from waypoint in doc.Elements("routes") select Convert.ToDouble(waypoint.Attribute("meters_per_second").Value)).First();

                _points = (from waypoint
                          in doc.Element("routes").Elements("route")
                           select new RoutePoint
                           {
                               Duration = Convert.ToDouble(waypoint.Attribute("duration").Value),
                               Heading = Convert.ToDouble(waypoint.Attribute("heading").Value),
                               Latitude = Convert.ToDouble(waypoint.Attribute("lat").Value),
                               Longitude = Convert.ToDouble(waypoint.Attribute("lon").Value),
                               TimesToFire = Convert.ToInt32((Convert.ToDouble(waypoint.Attribute("duration").Value) * 1000.0) / _updateInterval)
                           }).ToList();

                for (var idx = 0; idx < _points.Count - 1; ++idx)
                {
                    var thisPoint = _points[idx];
                    var nextPoint = _points[idx + 1];

                    _points[idx].LatDelta = (nextPoint.Latitude - thisPoint.Latitude) / _points[idx].TimesToFire;
                    _points[idx].LonDelta = (nextPoint.Longitude - thisPoint.Longitude) / _points[idx].TimesToFire;
                }
            }
        }

        void _timer_Tick(object sender, EventArgs e)
        {
            _lastUpdate = DateTime.MinValue;

            if (_currentStatus == GeoPositionStatus.Initializing)
            {
                _timer.Interval = TimeSpan.FromMilliseconds(_updateInterval);
                _currentStatus = GeoPositionStatus.Ready;
                if (StatusChanged != null)
                    StatusChanged(this, new GeoPositionStatusChangedEventArgs(_currentStatus));
            }

            _currentPosition = new GeoCoordinate(_currentPosition.Latitude + _points[_currentWayPoint].LatDelta, _currentPosition.Longitude + _points[_currentWayPoint].LonDelta);
            _currentPosition.Course = _points[_currentWayPoint].Heading;
            _currentPosition.Speed = _speed;

            PositionChanged(this, new GeoPositionChangedEventArgs<GeoCoordinate>(new GeoPosition<GeoCoordinate>(_lastUpdate, _currentPosition)));

            if (++_currentIndex == _points[_currentWayPoint].TimesToFire)
            {
                _currentIndex = 0;
                _currentWayPoint++;
                if (_currentWayPoint == _points.Count - 1)
                {
                    _timer.Stop();
                }
            }
        }

        public void Reset()
        {
            _currentWayPoint = 0;
        }

        public void Start(bool suppressPermissionPrompt)
        {
            Start();
        }

        public bool TryStart(bool suppressPermissionPrompt, TimeSpan timeout)
        {
            Start();
            return true;
        }

        public void Start()
        {
            _currentStatus = GeoPositionStatus.Initializing;
            _currentWayPoint = 0;
            _currentIndex = 0;
            _currentPosition = new GeoCoordinate(_points[_currentWayPoint].Latitude, _points[_currentWayPoint].Longitude);

            if (StatusChanged != null)
                StatusChanged(this, new GeoPositionStatusChangedEventArgs(_currentStatus));

            _timer.Interval = TimeSpan.FromMilliseconds(_pause);
            _timer.Start();
        }

        public void Stop()
        {
            _currentStatus = GeoPositionStatus.NoData;
            _timer.Stop();

            if (StatusChanged != null)
                StatusChanged(this, new GeoPositionStatusChangedEventArgs(_currentStatus));
        }

        public GeoPosition<GeoCoordinate> Position
        {
            get { return new GeoPosition<GeoCoordinate>() { Location = _currentPosition, Timestamp = _lastUpdate }; }
        }

        public GeoPositionStatus Status
        {
            get { return _currentStatus; }
        }

        private class RoutePoint
        {
            public double Latitude { get; set; }
            public double Longitude { get; set; }
            public double Duration { get; set; }
            public double Heading { get; set; }
            public int TimesToFire { get; set; }
            public double LatDelta { get; set; }
            public double LonDelta { get; set; }
        }
    }
}

